/*
 * Copyright (c) 2021 The red-star Project
 *
 * Licensed under the Apache License, version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inyourcode.example.cluster.lobby1;

import com.inyourcode.core.cluster.ClusterNodeConf;
import com.inyourcode.core.cluster.ClusterNodeManager;
import com.inyourcode.core.cluster.api.ClusterConst;
import com.inyourcode.core.monitor.MetricsConstants;
import com.inyourcode.core.monitor.MetricsService;
import com.inyourcode.core.util.JWTUtil;
import com.inyourcode.core.util.Preconditions;
import com.inyourcode.core.db.DataClassFilter;
import com.inyourcode.core.db.DataClassHolder;
import com.inyourcode.core.db.DataSaveProcessor;
import com.inyourcode.core.db.api.IDataClassHolder;
import com.inyourcode.core.db.mongo.MongoConfig;
import com.inyourcode.core.db.redis.RedisConfig;
import com.inyourcode.core.event.EventSystem;
import com.inyourcode.core.threads.api.ExecutorFactory;
import com.inyourcode.core.threads.ExecutorFactoryManager;
import com.inyourcode.core.threads.api.HashExecutor;
import com.inyourcode.core.transport.session.BasicTcpAcceptor;
import com.inyourcode.example.service.RedisScripter;
import com.inyourcode.core.transport.netty.JNettyTcpAcceptor;
import com.inyourcode.core.transport.rpc.ClusterServer;
import com.inyourcode.core.spring.ClassHelper;
import com.inyourcode.core.spring.JWTCondition;
import com.inyourcode.core.spring.TCPCondtion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.List;

/**
 * @author JackLei
 */
@Configurable
@Configuration
@EnableScheduling
@ComponentScan(basePackages = "${app.scanpackages}")
@Import({RedisConfig.class, MongoConfig.class})
public class LobbyNode {
    @Value("${cluster.node.join}")
    private String joinType;
    @Value("${cluster.node.type}")
    private String nodeType;
    @Value("${cluster.group.id}")
    private String clusterGroupId;
    @Value("${cluster.node.id}")
    private String nodeId;
    @Value("${cluster.node.name}")
    private String nodeName;
    @Value("${cluster.node.ip}")
    private String clusterIp;
    @Value("${cluster.node.maxLoad}")
    private int maxLoad;

    @Value("${jwt.secretKey}")
    private String jwtSecretKey;
    @Value("${jwt.issuer}")
    private String jwtIssuer;
    @Value("${jwt.expireTimeMillis}")
    private long jwtExpireTimeMillis;

    @Value("${tcp.listen.ip}")
    private String tcpListenIp;
    @Value("${tcp.listen.port}")
    private int tcpListenPort;
    @Value("${tcp.client.addr}")
    private String tcpClientAddr;

    @Value("${db.entity.path}")
    private String dbEntityPath;
    @Value("${db.storage.interval}")
    private int dbStorageInterval;
    @Value("${db.task.thread}")
    private int dbTaskThreadNum;

    @Value("${player.task.thread}")
    private int playerTaskThreadNum;

    @Autowired
    ClusterNodeManager clusterNodeManager;
    @Autowired
    ApplicationContext applicationContext;
    @Autowired
    DataSaveProcessor dataSaveProcessor;
    @Autowired
    @Qualifier(RedisConfig.STRING_REDIS_TEMPLATE)
    RedisTemplate redisTemplate;

    public static final String BEAN_NAME_DB_EXECUTOR = "DB_EXECUTOR";

    @PostConstruct
    public void init() {
        EventSystem.init(applicationContext);
        EventSystem.emit("start", null);

        String nodeKey = nodeType + "_" + nodeId;
        MetricsService.getInstance().start(nodeKey, redisTemplate);
    }

    @Bean
    public ClusterNodeConf clusterNodeConf() {
        Preconditions.checkNotNull(nodeType, "cluster type not found, " + nodeType);

        ClusterNodeConf clusterNodeInfo = new ClusterNodeConf();
        clusterNodeInfo.setUuid(String.valueOf(clusterNodeInfo.nextId()));
        clusterNodeInfo.setGroupId(clusterGroupId);
        clusterNodeInfo.setNodeId(nodeId);
        clusterNodeInfo.setNodeName(nodeName);
        clusterNodeInfo.setClusterIp(clusterIp);
        clusterNodeInfo.setMaxLoad(maxLoad);
        clusterNodeInfo.setNodeType(nodeType);
        clusterNodeInfo.getExtend().put(ClusterConst.KEY_CLUSTER_EXTEND_ADDR, tcpClientAddr);

        String[] split = joinType.split(",");
        for (String joinType : split) {
            clusterNodeInfo.getJoinClustTypes().add(joinType);
        }
        return clusterNodeInfo;
    }

    @Bean
    ClusterServer clusterNodeServer(ClusterNodeConf clusterNodeConf) throws InterruptedException {
        String clusterIp = clusterNodeConf.getClusterIp();
        String[] split = clusterIp.split(":");
        int port = Integer.valueOf(split[1]);
        ClusterServer rpcServer = new ClusterServer();
        rpcServer.withAcceptor(new JNettyTcpAcceptor(port));
        rpcServer.start(false);
        return rpcServer;
    }

    @Conditional(JWTCondition.class)
    @Bean
    JWTUtil jwtUtil(){
        return new JWTUtil(jwtSecretKey, jwtExpireTimeMillis, jwtIssuer);
    }

    @Conditional(TCPCondtion.class)
    @Bean
    BasicTcpAcceptor tcpAcceptor() throws InterruptedException {
        BasicTcpAcceptor tcpAcceptor = new BasicTcpAcceptor(new InetSocketAddress(tcpListenIp, tcpListenPort), playerTaskThreadNum);
        tcpAcceptor.start(false);
        return tcpAcceptor;
    }

    @Bean
    IDataClassHolder dataClassHolder() throws IOException, ClassNotFoundException, ClassHelper.ClassScannerException {
        List<Class> classList = ClassHelper.scan(dbEntityPath, new DataClassFilter());
        return new DataClassHolder(classList);
    }

    @Bean(BEAN_NAME_DB_EXECUTOR)
    HashExecutor dbExecutor() {
        return ExecutorFactoryManager.newExecutor(ExecutorFactory.Target.HASH);
    }

    @Bean
    DataSaveProcessor dataSaveProcessor(@Autowired
                                        @Qualifier(BEAN_NAME_DB_EXECUTOR)
                                        HashExecutor hashExecutor) {
        return new DataSaveProcessor(dbStorageInterval,hashExecutor);
    }

    @Bean
    RedisScripter redisScripter(RedisTemplate stringRedisTemplate) {
        return new RedisScripter(stringRedisTemplate);
    }

    @Scheduled(initialDelay = 5000, fixedRate = 1000)
    public void tick() {
        clusterNodeManager.tick();
    }

    @Scheduled(initialDelay = 5000, fixedRate = 1000)
    public void dbTick() {
        dataSaveProcessor.tick();
    }


}

